﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO.Ports;
using System.Linq;
using System.Text;

namespace Jodell.Transport.Modbus
{
    public class ModbusRtuClient : IModbusRtuClient
    {
        #region [Fields]
        private SerialPort mport;
        private readonly object sendLock = new object();
        private DateTime lastTransportTime = DateTime.Now;
        #endregion

        #region [Properties]
        ///<inheritdoc/>
        public bool IsConnected { get { return mport?.IsOpen ?? false; } }
        ///<inheritdoc/>
        public string PortName { get; set; }
        ///<inheritdoc/>
        public int BaudRate { get; set; } = 115200;
        ///<inheritdoc/>
        public Parity Parity { get; set; } = Parity.None;
        ///<inheritdoc/>
        public StopBits StopBits { get; set; } = StopBits.One;
        ///<inheritdoc/>
        public int WriteTimeout { get; set; } = 1000;
        ///<inheritdoc/>
        public int ReadTimeout { get; set; } = 1000;
        ///<inheritdoc/>
        public IModbusResolver Resolver { get; set; }
        public int Cycle { get; private set; }
        #endregion

        #region [CTOR]
        public ModbusRtuClient(IModbusResolver resolver)
        {
            this.Resolver = resolver;
        }
        #endregion

        #region [Functions]
        private void Init()
        {
            mport = new SerialPort();
            mport.BaudRate = this.BaudRate;
            mport.Parity = this.Parity;
            mport.StopBits = this.StopBits;
            mport.PortName = this.PortName;
            mport.Open();
            mport.BaseStream.ReadTimeout = this.ReadTimeout;
            mport.BaseStream.WriteTimeout = this.WriteTimeout;
        }



        /// <summary>
        /// 数据发送
        /// </summary>
        /// <param name="datas"></param>
        /// <returns></returns>
        private ModbusRecInfo SendData(byte[] datas)
        {
            lastTransportTime = DateTime.Now;
            ModbusRecInfo info = null;
            DateTime time = DateTime.Now;
            try
            {
                lock (sendLock)
                {
                    Debug.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] 发送 {datas._ToString()}");
                    mport.Write(datas, 0, datas.Length);

                    if (datas[0] == 0x00) //广播地址 不等待响应
                    {
                        return null;
                    }
                    Stopwatch watch = new Stopwatch();
                    Resolver.Reset();
                    byte[] buffer = new byte[256];
                    watch.Start();
                    while (watch.ElapsedMilliseconds <= ReadTimeout)
                    {
                        if (mport.BytesToRead > 0)
                        {
                            var len = mport.Read(buffer, 0, 256);
                            StringBuilder sb = new StringBuilder();
                            for (int i = 0; i < len; i++)
                            {
                                sb.Append(buffer[i].ToString("X2"));
                                sb.Append(' ');
                            }
                            Debug.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] 接收 {sb}");
                            info = Resolver.AddRange(buffer, len);
                            if (info.Ret)
                                break;
                        }
                    }
                    if (watch.ElapsedMilliseconds > ReadTimeout)
                    {
                        throw new TimeoutException("接收超时");
                    }
                }
            }
            catch
            {
                throw;
            }
            this.Cycle = (int)(DateTime.Now - lastTransportTime).TotalMilliseconds;
            return info;
        }
        public void Connect()
        {
            Init();
        }
        public void Disconnect()
        {
            mport?.Close();
        }
        /// <inheritdoc/>
        public byte[] ReadHoldingRegisters(byte unitIdentifier, ushort startingAddress, ushort quantity)
        {
            if (!this.IsConnected)
            {
                throw new NotConnectedException();
            }
            List<byte> list = new List<byte>
            {
                unitIdentifier,
                (byte)ModbusFunctionCode.ReadHoldingRegisters
            };
            list.AddReverse(startingAddress);
            list.AddReverse(quantity);
            var crc = CrcUtil.Crc(list);
            list.Add(crc);
            return SendData(list.ToArray()).RecData.Take(quantity * 2).ToArray(); ;
        }


        public byte[] ReadInputRegisters(byte unitIdentifier, ushort startingAddress, ushort quantity)
        {
            if (!this.IsConnected)
            {
                throw new NotConnectedException();
            }
            List<byte> list = new List<byte>
            {
                unitIdentifier,
                (byte)ModbusFunctionCode.ReadInputRegisters
            };
            list.AddReverse(startingAddress);
            list.AddReverse(quantity);
            var crc = CrcUtil.Crc(list);
            list.Add(crc);
            return SendData(list.ToArray()).RecData.Take(quantity * 2).ToArray(); ;
        }
        /// <inheritdoc/>
        public void WriteMultipleRegisters(byte unitIdentifier, ushort startingAddress, byte[] dataset)
        {
            if (!this.IsConnected)
            {
                throw new NotConnectedException();
            }
            var quantity = (UInt16)(dataset.Length / 2);
            List<byte> list = new List<byte>
            {
                unitIdentifier,
                (byte)ModbusFunctionCode.WriteMultipleRegisters
            };
            list.AddReverse(startingAddress);
            list.AddReverse(quantity);
            list.Add((byte)(quantity * 2));
            list.AddRange(dataset);
            var crc = CrcUtil.Crc(list);
            list.Add(crc);
            SendData(list.ToArray());
        }
        /// <inheritdoc/>
        public byte[] WriteFileRecord(byte unitIdentifier, byte deviceNo, UInt16 recordId, UInt16 regLength, byte[] data)
        {
            if (!this.IsConnected)
            {
                throw new NotConnectedException();
            }
            List<byte> list = new List<byte>
            {
                unitIdentifier,
                (byte)ModbusFunctionCode.WriteFileRecord,
                0x00,
                deviceNo,
                0x00,
                0x00
            };
            list.AddReverse(recordId);      //byte6-7
            list.AddReverse(regLength);     //byte8-9

            for (int i = 0; i < regLength; i++)
            {
                list.Add(data[i * 2]);
                list.Add(data[i * 2 + 1]);
            }
            var crc = CrcUtil.Crc(list);
            list.Add(crc);
            //SendData(list.ToArray());
            return SendData(list.ToArray()).RecData.Take(7).ToArray(); 
        }
        #endregion
    }
}
